﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging;
using System.Windows.Forms;
using System.Xml.Serialization;
using Interfaces;

namespace Peer
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new PeerForm());
        }
    }

    #region IMPLEMENTAÇÃO DE PEER
    public class Peer : MarshalByRefObject, IPeer, ISuperPeer
    {
        public string Name { get; set; }
        public string Subject { get; set; }
        public ISuperPeer SuperPeer { get; private set; }
        public bool IsSuperPeer { get; private set; }
        public IEnumerable<Article> Articles { get; private set; }
        public IDictionary<string, IDictionary<string, IPeer>> KnownPeers { get; private set; }
        private readonly PeerForm _form;
        private ObjRef RefObj { get; set; }

        //public Peer() : this(new List<Article>(), null)
        //{
        //}

        public override object InitializeLifetimeService()
        {
            return null; //Tempo de vida infinito
        }

        public Peer(IEnumerable<Article> articles, PeerForm form, string subject)        {
            Subject = subject;
            IsSuperPeer = false;
            Articles = articles;
            KnownPeers = new SortedDictionary<string, IDictionary<string, IPeer>>();
            _form = form;
        }

        #region DELEGATES

        private delegate IDictionary<string, IPeer> GetPeersDelegateSuperPeer(string subject, string superPeerName, IPeer target);

        #endregion DELEGATES

        #region MÉTODOS DE PEER

        /// <summary>
        /// Método para pesquisa de assuntos.
        /// igual ao get de KnownArticles?!?!?
        /// </summary>
        /// <returns></returns>
        public IEnumerable GetArticles()
        {
            _form.UpdateRequestsTextBoxByCallBack("GetArticles");
            return Articles;
        }

        /// <summary>
        /// Método para pedir, ao SuperPeer, Peers que possuam artigos com o assunto indicado.
        /// </summary>
        /// <param name="subject"></param>
        /// <returns>Retorna os Peers que o SuperPeer tem que conheçam o assunto indicado no parâmetro.</returns>
        public IDictionary<string, IPeer> GetPeersFor(string subject)
        {
            // Se este Peer for SuperPeer e não estiver ligado a outro SuperPeer
            if (!SuperPeer.Equals(this))
            {
                var @delegate = new GetPeersDelegateSuperPeer(SuperPeer.GetPeersFor);
                @delegate.BeginInvoke(subject, null, this, CallbackRegisterPeersFor, subject);
            }
            
            // TODO Verificar este lock
            IDictionary<string, IPeer> ret;
            lock (KnownPeers)
            {
                ret = KnownPeers.ContainsKey(subject) ? new Dictionary<string, IPeer>(KnownPeers[subject]) : new Dictionary<string, IPeer>();
                if(Subject.Equals(subject)) ret.Add(Name, this);
            }
            return ret;
        }

        /// <summary>
        /// Método de callback passado aquando do BeginInvoke executado no método GetPeersAsync.
        /// Regista um conjunto de Peers que possuam artigos com assunto correspondente ao deste Peer.
        /// </summary>
        /// <param name="ar"></param>
        public void CallbackRegisterPeersFor(IAsyncResult ar)
        {
            var subject = ar.AsyncState as string;
            if(subject == null)
                throw new ApplicationException("Erro na execução do método CallbackRegisterPeersFor: AsyncState não é do tipo string.");

            var del = (((AsyncResult) ar).AsyncDelegate) as GetPeersDelegateSuperPeer;
            if(del == null)
                throw new ApplicationException("Erro na execução do método CallbackRegisterPeersFor: AsyncDelegate não é do tipo GetPeersDelegateSuperPeer.");

            IDictionary<string, IPeer> peers = del.EndInvoke(ar);

            _form.UpdateRequestsTextBoxByCallBack(string.Format("CallbackRegisterPeersFor: Assunto - {0}.", subject));
            
            // Actualiza lista de Peers conhecidos
            UpdateKnownPeers(subject, peers);
            
            // Executa método para actualização dos controlos gráficos que contêm os Peers encontrados e conhecidos.
            _form.UpdateFoundPeersListBoxByCallBack(peers.Keys.ToArray());
            _form.UpdateKnownPeersListBoxByCallBack(subject);
        }

        /// <summary>
        /// Promove-se a SuperPeer.
        /// </summary>
        /// <exception cref="RemotingException">Caso não consiga obter ligação com o SuperPeer.</exception>
        public void PromoteToSuperPeer()
        {
            RemotingConfiguration.RegisterWellKnownServiceType(typeof(Peer), Name + ".rem", WellKnownObjectMode.Singleton);
            Url = RemotingConfiguration.GetRegisteredWellKnownServiceTypes()[0].ObjectUri;
            RefObj = RemotingServices.Marshal(this, Url);

            // TODO tratar exceção aquando da ligação ao SuperPeer -> quem trata as exceções é quem chama este método!!!???
            // SuperPeer só é == null aquando da primeira ligação
            SuperPeer = SuperPeer == null ? this : SuperPeer.PromoteToSuperPeer(this);
            IsSuperPeer = true;
        }

        /// <summary>
        /// Serve para registar superPeer como SuperPeer deste Peer e para registar este Peer com Peer deste superPeer.
        /// </summary>
        /// <param name="superPeer"></param>
        /// <param name="subject"></param>
        /// <exception cref="RemotingException">Caso não consiga obter ligação com o SuperPeer.</exception>
        public void SetSuperPeer(ISuperPeer superPeer, string subject)
        {
            SuperPeer = superPeer;
            // TODO tratar exceção aquando da ligação ao SuperPeer -> quem trata as exceções é quem chama este método!!!???
            superPeer.RegisterPeerFor(subject, Name, this);
        }

        /// <summary>
        /// Serve para desligar este Peer dos sistema, notificando os Peers e SuperPeer com os quais este Peer esteja ligado.
        /// </summary>
        // TODO passar para a região dos delegates
        private delegate void UnregisterPeerDelegate(string name);
        public void Disconnect()
        {
            if (!SuperPeer.Equals(this))
            {
                var del = new UnregisterPeerDelegate(SuperPeer.UnregisterPeer);
                del.BeginInvoke(Name, null, null);
                //this.SuperPeer.UnregisterPeer(this.Name);
            }
            RemotingServices.Disconnect(this);
        }
        #endregion 

        #region MÉTODOS DE SUPERPEER

        public string Url { get; set; }

        /// <summary>
        /// Método para adicionar um novo peer.
        /// </summary>
        /// <param name="subject"></param>
        /// <param name="peerName"></param>
        /// <param name="peer"></param>
        /// <exception cref="NotSupportedException">Se este Peer não for SuperPeer.</exception>
        public void RegisterPeerFor(string subject, string peerName, IPeer peer)
        {
            if(!IsSuperPeer)
                throw new NotSupportedException("Este Peer não é SuperPeer. Não tem autorização para executar o método RegisterPeerFor.");

            UpdateKnownPeers(subject, new Dictionary<string, IPeer>{{peerName, peer}});
            _form.UpdateRequestsTextBoxByCallBack(String.Format("RegisterPeerFor: Assunto = {0} | Peer = {1}", subject, peerName));
        }

        /// <summary>
        /// Método usado, pelos Peers que estiverem ligados a este SuperPeer, para indicar que o Peer vai sair do sistema.
        /// Este SuperPeer passa a não indicar este Peer nas próximas pesquisas GetPeersFor
        /// </summary>
        /// <param name="peerName"></param>
        public void UnregisterPeer(string peerName)
        {
            // TODO Verificar
            lock(KnownPeers)
            {
                foreach (var value in KnownPeers.Values)
                    value.Remove(peerName);
            }

            _form.UpdateRequestsTextBoxByCallBack(String.Format("UnRegisterPeer: Peer = {0}", peerName));
        }

        // É igual ao método com a mesma assinatura de IPeer
        /// <summary>
        /// Método que devolve uma lista.
        /// </summary>
        /// <param name="subject"></param>
        /// <param name="superPeerName"></param>
        /// <param name="target"></param>
        /// <returns>Retorna os Peers que o SuperPeer tem que conheçam o assunto indicado no parâmetro.</returns>
        IDictionary<string, IPeer> ISuperPeer.GetPeersFor(string subject, string superPeerName, IPeer target)
        {
            if (!SuperPeer.Equals(this))
            {
                if (Name.Equals(superPeerName)) // significa que o pedido já deu a volta na rede de SuperPeers
                {
                    return new Dictionary<string, IPeer>();
                }

                var @delegate = new GetPeersDelegateSuperPeer(SuperPeer.GetPeersFor);
                @delegate.BeginInvoke(subject, superPeerName ?? Name, target, target.CallbackRegisterPeersFor, subject);
            }

            _form.UpdateRequestsTextBoxByCallBack(String.Format("GetPeersFor: Assunto = {0} | Peer = {1} | Origem = {2}", subject, superPeerName ?? Name, target.Name));

            // TODO Verificar este lock
            IDictionary<string, IPeer> ret;
            lock (KnownPeers)
            {
                ret = KnownPeers.ContainsKey(subject) ? new Dictionary<string, IPeer>(KnownPeers[subject]) : new Dictionary<string, IPeer>();
                if (Subject.Equals(subject)) ret.Add(Name, this);
            }

            return ret;
        }

        /// <summary>
        /// Método para gerir a promoção de novos peers. Retorna o SuperPeer ao qual o Peer vai ficar ligado.
        /// </summary>
        /// <param name="peer"></param>
        /// <returns></returns>
        /// <exception cref="NotSupportedException">Se este Peer não for SuperPeer.</exception>
        public ISuperPeer PromoteToSuperPeer(IPeer peer)
        {
            if (!IsSuperPeer)
                throw new NotSupportedException("Este Peer não é SuperPeer. Não tem autorização para executar o método PromoteToSuperPeer.");

            // TODO Verificar
            ISuperPeer ret;
            lock (SuperPeer)
            {
                ret = SuperPeer;
                SuperPeer = peer as ISuperPeer;
            }

            _form.UpdateRequestsTextBoxByCallBack(String.Format("PromoteToSuperPeer: Peer = {0}", peer.Name));

            return ret;
        }
        #endregion MÉTODOS DE SUPERPEER

        #region MÉTODOS AUXILIARES

        private void UpdateKnownPeers(string subject, IDictionary<string, IPeer> peers)
        {
            lock(KnownPeers)
            {
                if (!KnownPeers.ContainsKey(subject))
                    KnownPeers[subject] = new Dictionary<string, IPeer>();

                foreach (var peer in peers)
                    KnownPeers[subject].Add(peer);
            }
        }
        #endregion MÉTODOS AUXILIARES
    }

    #endregion

    #region IMPLEMENTAÇÃO DE ARTICLE
    [Serializable]
    public class Article : IArticle
    {
        [XmlElement("Title")]
        public string Title { get; set; }

        [XmlArray("Authors")]
        [XmlArrayItem("Author")]
        public string[] Authors { get; set; }

        [XmlElement("Year")]
        public int YearOfPublication { get; set; }

        [XmlElement("Resume")]
        public string Resume { get; set; }
    }
    #endregion

}
